/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package za.co.pas.jtorrenttracker;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Random;
import za.co.pas.jtorrentparser.parser.FileFormatException;
import za.co.pas.jtorrentparser.parser.IllegalCharacterException;
import za.co.pas.jtorrenttracker.util.Utils;
import sun.misc.BASE64Encoder;
import za.co.pas.jtorrentparser.parser.TorrentParser;

/**
 *
 * @author alabuschagne
 */
public class TrackerConnector
{
    private String proxyHost;
    private int proxyPort = -1;
    private String proxyUser;
    private String proxyPass;

    public TrackerConnector()
    {
    }

    public void setProxy(String proxyHost, int proxyPort, String proxyUser, String proxyPass)
    {
        this.proxyHost = proxyHost;
        this.proxyPort = proxyPort;
        this.proxyUser = proxyUser;
        this.proxyPass = proxyPass;
    }

//    public void getProxyFromSystemProperties()
//    {
//        this.proxyHost = System.getProperty("http.proxyHost");
//        String tmp = System.getProperty("http.proxyPort");
//        if(tmp != null)
//        {
//            try
//            {
//                this.proxyPort = Integer.parseInt(tmp);
//            }
//            catch(NumberFormatException nfe)
//            {
//                System.err.println(nfe.getMessage());
//            }
//        }
//    }
    private Proxy doProxy()
    {
        if((proxyHost == null) || (proxyPort == -1))
        {
            return null;
        }
        return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
    }

    private String makeUsrPwd()
    {
        if((proxyUser == null) || (proxyPass == null))
        {
            return null;
        }
        StringBuilder sb = new StringBuilder(proxyUser);
        sb.append(":");
        sb.append(proxyPass);
        return sb.toString();
    }

    private String hEndoced(short[] chars)
    {
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < chars.length; i++)
        {
            sb.append("%");
            String str = Integer.toHexString(chars[i]);
            if(str.length() < 2)
            {
                sb.append("0");
            }
            sb.append(str);
        }
        return sb.toString();
    }

    private String makePeerID()
    {
        String str = "jTorrentTracker";
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("HHmm");
        str += sdf.format(cal.getTime());
        Random r = new Random(System.currentTimeMillis());
        str += Integer.toString(r.nextInt(10));
        return str;
    }

    /**
     * 
     * @param strURL
     * @param infohash
     * @param port
     * @param downloaded
     * @param left
     * @param event
     * @return
     * @throws MalformedURLException
     * @throws IOException
     * @throws IllegalCharacterException
     * @throws FileFormatException
     */
    public Map<String, Object> getTrackerInfo(String strURL,
            short[] infohash,
            int port,
            int downloaded,
            int left,
            String event)
            throws MalformedURLException, IOException, IllegalCharacterException, FileFormatException
    {
        strURL = strURL.toLowerCase();
        StringBuilder sbURLString = new StringBuilder(strURL);
        sbURLString.append("?");
        sbURLString.append("info_hash=");
        sbURLString.append(hEndoced(infohash));
        sbURLString.append("&peer_id=");
        sbURLString.append(makePeerID());
        sbURLString.append("&port=");
        sbURLString.append(Integer.toString(port));//6881
        sbURLString.append("&downloaded=");
        sbURLString.append(Integer.toString(downloaded));
        sbURLString.append("&left=");
        sbURLString.append(Integer.toString(left));
        if(event != null)
        {
            sbURLString.append("&event=");
            sbURLString.append(event);
        }
        if(strURL.startsWith("http"))
        {
            //URLEncoder

            URL httpURL = new URL(sbURLString.toString());
            //System.out.println(httpURL.getPort());
            URLConnection httpCon = null;
            Proxy proxy = doProxy();
            if(proxy == null)
            {
                httpCon = httpURL.openConnection();
            }
            else
            {
                httpCon = httpURL.openConnection(proxy);
            }
            String usrPwd = makeUsrPwd();
            if(usrPwd != null)
            {
                BASE64Encoder encoder = new BASE64Encoder();
                String encoded = encoder.encode(usrPwd.getBytes());
                httpCon.setRequestProperty("Proxy-Authorization", "Basic " + encoded);
            }
            BufferedReader in = new BufferedReader(new InputStreamReader(httpCon.getInputStream()));
            char[] array = new char[1];

            array[0] = 0x00;
            List<char[]> arrayList = new LinkedList<char[]>();
            while(in.read(array) != -1)
            {
                arrayList.add(array);
                System.out.print(array);
                array = new char[1];
                array[0] = 0x00;
            }
            if(array[0] != 0x00)
            {
                arrayList.add(array);
            }
            //just to make sure
            char[] checkLast = arrayList.get(arrayList.size() - 1);
            if(checkLast[0] != 'e')
            {
                checkLast[0] = 'e';
                arrayList.add(checkLast);
            }
            System.out.println();
            in.close();
            int size = arrayList.size();
            short[] bytes = new short[size];
            int count = 0;
            ListIterator<char[]> it = arrayList.listIterator();
            while(it.hasNext())
            {
                char[] c = it.next();
                bytes[count] = (short) c[0];
                count++;
            }
            Map<String, Object> map = TorrentParser.parse(bytes);
            debugMap(map, 0);
            return map;
//            String inputLine;
//
//            while((inputLine = in.readLine()) != null)
//            {
//                System.out.println(inputLine);
//            }
        }
        else if(strURL.startsWith("udp"))
        {
            return null;
//            DatagramSocket socket = new DatagramSocket();
//
//            // send request
//            byte[] buf = new byte[256];
//            InetAddress address = InetAddress.getByName(strURL);
//            DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 4445);
//            socket.send(packet);
//
//            // get response
//            packet = new DatagramPacket(buf, buf.length);
//            socket.receive(packet);
//
//            // display response
//            String received = new String(packet.getData(), 0, packet.getLength());
//            System.out.println(received);
//
//            socket.close();
        }
        return null;
    }

    private void indent(int ind)
    {
        for(int i = 0; i < ind; i++)
        {
            System.out.print("-");
        }
    }

    private void debugMap(Map map, int indent)
    {
        Iterator<String> it = map.keySet().iterator();
        while(it.hasNext())
        {
            String key = it.next();
            indent(indent);
            System.out.println("Key: " + key);
            if(map.get(key) instanceof Map)
            {
                int i = indent + 1;
                debugMap((Map) map.get(key), i);
            }
            if(map.get(key) instanceof short[])
            {
                short[] ss = (short[]) map.get(key);
                boolean unprintable = false;
                for(int i = 0; i < ss.length; i++)
                {
                    char c = (char) ss[i];
                    if((c < 32) || (c > 126))
                    {
                        unprintable = true;
                        break;//out of the for loop
                    }
                }
                if(!unprintable)
                {
                    indent(indent);
                    System.out.println("Value: " + map.get(key).toString());
                }
                else
                {
                    indent(indent);
                    System.out.print("Value: ");
                    for(int i = 0; i < ss.length; i++)
                    {
                        ss[i] = (short) (0x000000FF & ((int) ss[i]));
                        String str = Integer.toHexString(ss[i]);
                        if(str.length() < 2)
                        {
                            str = "0" + str;
                        }
                        str = str.toUpperCase();
                        System.out.print("0x" + str + " ");
                    }
                    System.out.println();
                }
            }
            else
            {
                indent(indent);
                System.out.println("Value: " + map.get(key).toString());
            }
        }
    }

    public static String[] getPeerIPPort(Map<String, Object> trackerResponse)
    {
        if(trackerResponse.containsKey("peers"))
        {
            if(trackerResponse.get("peers") instanceof short[])
            {
                //this is easy
                short[] ss = (short[]) trackerResponse.get("peers");
                boolean unprintable = false;
                for(int i = 0; i < ss.length; i++)
                {
                    char c = (char) ss[i];
                    if((c < 32) || (c > 126))
                    {
                        unprintable = true;
                        break;//out of the for loop
                    }
                }
                if(!unprintable)
                {
                    //This is not good?
                    System.err.println("Value: " + trackerResponse.get("peers").toString());
                    return null;
                }
                else
                {
                    StringBuilder sb = new StringBuilder();
                    for(int i = 0; i < ss.length - 2; i++)
                    {
                        ss[i] = (short) (0x000000FF & ((int) ss[i]));
                        sb.append(Integer.toString(ss[i]));
                        if(i < ss.length - 3)
                        {
                            sb.append(".");
                        }
                    }
                    int index = ss.length - 2;
                    ss[index] = (short) (0x000000FF & ((int) ss[index]));
                    String strPort1 = Integer.toHexString(ss[index]);
                    index++;
                    ss[index] = (short) (0x000000FF & ((int) ss[index]));
                    String strPort2 = Integer.toHexString(ss[index]);
                    if(strPort2.length() < 2)
                    {
                        strPort2 = "0" + strPort2;
                    }
                    String strPort = strPort1 + strPort2;
                    try
                    {
                        int port = Integer.parseInt(strPort, 16);
                        sb.append(":");
                        sb.append(Integer.toString(port));
                    }
                    catch(NumberFormatException nfe)
                    {
                        System.err.println(nfe.getMessage());
                        return null;
                    }
                    String [] ret = {sb.toString()};
                    return ret;
                }
            }
            else
            {
                //TODO
                return null;
            }
        }
        else
        {
            return null;
        }
    }
}
